package top.ieei.demo.dhcp;

import java.io.Serializable;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 无类型域间选路，Classless Inter-Domain Routing
 */
public class InetCidr implements Serializable, Comparable<InetCidr>
{
    private static final long serialVersionUID = 1L;

    private final int addr;

    private final int mask;

    private static final String[] CIDR_MASKS;

    private static final Map<InetAddress, Integer> gCidr;

    private static final long[] gCidrMask;

    public InetCidr(InetAddress addr, int mask)
    {
        if (addr == null)
        {
            throw new NullPointerException("addr is null");
        }
        if (!(addr instanceof Inet4Address))
        {
            throw new IllegalArgumentException("Only IPv4 addresses supported");
        }
        if ((mask < 1) || (mask > 32))
        {
            throw new IllegalArgumentException("Bad mask:" + mask + " must be between 1 and 32");
        }

        this.addr = (DhcpUtil.inetAddress2Int(addr) & (int) gCidrMask[mask]);
        this.mask = mask;
    }

    public InetCidr(InetAddress addr, InetAddress netMask)
    {
        if ((addr == null) || (netMask == null))
        {
            throw new NullPointerException();
        }
        if ((!(addr instanceof Inet4Address)) || (!(netMask instanceof Inet4Address)))
        {
            throw new IllegalArgumentException("Only IPv4 addresses supported");
        }
        Integer intMask = (Integer) gCidr.get(netMask);
        if (intMask == null)
        {
            throw new IllegalArgumentException("netmask: " + netMask + " is not a valid mask");
        }
        this.addr = (DhcpUtil.inetAddress2Int(addr) & (int) gCidrMask[intMask.intValue()]);
        this.mask = intMask.intValue();
    }

    public String toString()
    {
        return DhcpUtil.int2InetAddress(this.addr).getHostAddress() + '/' + this.mask;
    }

    public InetAddress getAddr()
    {
        return DhcpUtil.int2InetAddress(this.addr);
    }

    public long getAddrLong()
    {
        return this.addr & 0xFFFFFFFF;
    }

    public int getMask()
    {
        return this.mask;
    }

    public final long toLong()
    {
        return (this.addr & 0xFFFFFFFF) + (this.mask << 32);
    }

    public static final InetCidr fromLong(long l)
    {
        if (l < 0L)
        {
            throw new IllegalArgumentException("l must not be negative: " + l);
        }
        long ip = l & 0xFFFFFFFF;
        long mask = l >> 32;
        return new InetCidr(DhcpUtil.long2InetAddress(ip), (int) mask);
    }

    public int hashCode()
    {
        return this.addr ^ this.mask;
    }

    public boolean equals(Object obj)
    {
        if ((obj == null) || (!(obj instanceof InetCidr)))
        {
            return false;
        }
        InetCidr cidr = (InetCidr) obj;

        return (this.addr == cidr.addr) && (this.mask == cidr.mask);
    }

    public static InetCidr[] addr2Cidr(InetAddress addr)
    {
        if (addr == null)
        {
            throw new IllegalArgumentException("addr must not be null");
        }
        if (!(addr instanceof Inet4Address))
        {
            throw new IllegalArgumentException("Only IPv4 addresses supported");
        }
        int addrInt = DhcpUtil.inetAddress2Int(addr);
        InetCidr[] cidrs = new InetCidr[32];

        for (int i = cidrs.length; i >= 1; i--)
        {
            cidrs[(32 - i)] = new InetCidr(DhcpUtil.int2InetAddress(addrInt & (int) gCidrMask[i]), i);
        }
        return cidrs;
    }

    public int compareTo(InetCidr o)
    {
        if (o == null)
        {
            throw new NullPointerException();
        }
        if (equals(o))
        {
            return 0;
        }
        if (int2UnsignedLong(this.addr) < int2UnsignedLong(o.addr))
            return -1;
        if (int2UnsignedLong(this.addr) > int2UnsignedLong(o.addr))
        {
            return 1;
        }
        if (this.mask < o.mask)
            return -1;
        if (this.mask > o.mask)
        {
            return 1;
        }
        return 0;
    }

    private static final long int2UnsignedLong(int i)
    {
        return i & 0xFFFFFFFF;
    }

    public static boolean isSorted(List<InetCidr> list)
    {
        if (list == null)
        {
            return true;
        }
        InetCidr pivot = null;
        for (InetCidr cidr : list)
        {
            if (cidr == null)
            {
                throw new NullPointerException();
            }
            if (pivot == null)
            {
                pivot = cidr;
            }
            else
            {
                if (pivot.compareTo(cidr) >= 0)
                {
                    return false;
                }
                pivot = cidr;
            }
        }
        return true;
    }

    public static void checkNoOverlap(List<InetCidr> list)
    {
        if (list == null)
            return;
        assert (isSorted(list));
        InetCidr prev = null;
        long pivotEnd = -1L;
        for (InetCidr cidr : list)
        {
            if (cidr == null)
            {
                throw new NullPointerException();
            }
            if ((prev != null) && (cidr.getAddrLong() <= pivotEnd))
            {
                throw new IllegalStateException("Overlapping cidr: " + prev + ", " + cidr);
            }
            pivotEnd = cidr.getAddrLong() + (gCidrMask[cidr.getMask()] ^ 0xFFFFFFFF);
            prev = cidr;
        }
    }

    static
    {
        CIDR_MASKS = new String[]{"128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0", "248.0.0.0", "252.0.0.0",
                "254.0.0.0", "255.0.0.0", "255.128.0.0", "255.192.0.0", "255.224.0.0", "255.240.0.0", "255.248.0.0",
                "255.252.0.0", "255.254.0.0", "255.255.0.0", "255.255.128.0", "255.255.192.0", "255.255.224.0",
                "255.255.240.0", "255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0", "255.255.255.128",
                "255.255.255.192", "255.255.255.224", "255.255.255.240", "255.255.255.248", "255.255.255.252",
                "255.255.255.254", "255.255.255.255"};

        gCidr = new HashMap<InetAddress, Integer>(48);
        gCidrMask = new long[33];
        try
        {
            gCidrMask[0] = 0L;
            for (int i = 0; i < CIDR_MASKS.length; i++)
            {
                InetAddress mask = InetAddress.getByName(CIDR_MASKS[i]);

                gCidrMask[(i + 1)] = DhcpUtil.inetAddress2Long(mask);
                gCidr.put(mask, Integer.valueOf(i + 1));
            }
        }
        catch (UnknownHostException e)
        {
            throw new IllegalStateException("Unable to initialize CIDR");
        }
    }
}